const std = @import("std");
const curses = @cImport({
	@cInclude("curses.h");
});

const gridLib = @import("grid.zig");
const Grid = @import("grid.zig").Grid;
const Player = @import("player.zig").Player;
const Ball = @import("ball.zig").Ball;
const Blocks = @import("blocks.zig").Blocks;
const Block = @import("blocks.zig").Block;

const EndCondition = enum {WON, LOST};


pub fn main_loop(speed_mult: u8, grid: *Grid, player: *Player, ball: *Ball,
		blocks: *Blocks) !void {
	while (true) {
		const ended: ?EndCondition = step(grid, player, ball, blocks);
		std.time.sleep(std.time.ns_per_s / @as(u64, speed_mult));

		if (ended) |condition| {
			switch (condition) {
				EndCondition.WON => {
					game_won();
					break;
				},
				EndCondition.LOST => {
					game_over();
					break;
				},
			}
		}
	}
}


fn step(grid: *Grid, player: *Player, ball: *Ball, blocks: *Blocks) ?EndCondition {
	grid.clear();
	move_player(grid, player);
	const ended: ?EndCondition = move_ball(grid, player, ball, blocks);
	set_grid_cells(grid, player, ball, blocks);
	_ = curses.clear();
	grid.display();

	return ended;
}


fn move_player(grid: *Grid, player: *Player) void {
	_ = curses.getch();
	_ = curses.getch();
	const key: c_int = curses.getch();
	_ = curses.flushinp();

	switch (key) {
		67 => player.position +|= 1,
		68 => player.position -|= 1,
		else => {},
	}

	player.position = @min(player.position, grid.width -| player.length);
}

fn move_ball(grid: *Grid, player: *Player, ball: *Ball, blocks: *Blocks)
		?EndCondition {
	if (blocks.empty()) {
		return EndCondition.WON;
	}
	if (ball.y >= grid.height) {
		return EndCondition.LOST;
	}

	// Wall collisions
	if (ball.x >= grid.width - 1 and ball.dx > 0) {
		ball.dx = -ball.dx;
	} else if (ball.x == 0 and ball.dx < 0) {
		ball.dx = -ball.dx;
	}
	if (ball.y == 0 and ball.dy < 0) {
		ball.dy = -ball.dy;
	}

	// Player collisions
	if (player.colliding(
			u8_add_i8(ball.x, ball.dx),
			u8_add_i8(ball.y, ball.dy))
	) {
		const rebound_calc: i32 = @as(i32, 2 * u8_add_i8(ball.x, ball.dx))
				- player.start() - player.end();
		ball.dy = -ball.dy;
		ball.dx = if (rebound_calc < 0) -1 else 1;
	}

	// Block collisions
	var collided: bool = false;
	var x: u8 = u8_add_i8(ball.x, ball.dx);
	var y: u8 = ball.y;
	if (blocks.colliding(x, y)) {
		_ = blocks.remove_block(x, y);
		ball.dx *= -1;
		collided = true;
	}
	x = ball.x;
	y = u8_add_i8(ball.y,  ball.dy);
	if (blocks.colliding(x, y)) {
		_ = blocks.remove_block(x, y);
		ball.dy *= -1;
		collided = true;
	}
	if (!collided) {
		x = u8_add_i8(ball.x, ball.dx);
		y = u8_add_i8(ball.y,  ball.dy);
		if (blocks.colliding(x, y)) {
			_ = blocks.remove_block(x, y);
			ball.dy *= -1;
			collided = true;
		}
	}

	ball.x = u8_add_i8(ball.x, ball.dx);
	ball.y = u8_add_i8(ball.y, ball.dy);

	return null;
}


fn set_grid_cells(grid: *Grid, player: *Player, ball: *Ball, blocks: *Blocks)
		void {
	for (player.start()..player.end() + 1) |position| {
		grid.set_cell(@truncate(position), grid.height - 1,
				gridLib.CellTypes.PLAYER);
	}

	var block_iter = blocks.get_blocks();
	while (block_iter.next()) |entry| {
		const block: *Block = entry.value_ptr;
		grid.set_cell(block.x, block.y, gridLib.CellTypes.BLOCK);
	}

	grid.set_cell(ball.x, ball.y, gridLib.CellTypes.BALL);
}


fn game_over() void {
	curses.timeout(-1);

	_ = curses.clear();
	_ = curses.attron(curses.COLOR_PAIR(1));
	_ = curses.addstr("\tGAME OVER\n\rPress ENTER to continue");

	var key: c_int = curses.getch();
	while (key != 10) {
		key = curses.getch();
	}

	curses.timeout(0);
}


fn game_won() void {
	curses.timeout(-1);

	_ = curses.clear();
	_ = curses.attron(curses.COLOR_PAIR(1));
	_ = curses.addstr("\tYOU WIN\n\rPress ENTER to continue");

	var key: c_int = curses.getch();
	while (key != 10) {
		key = curses.getch();
	}

	curses.timeout(0);
}


fn u8_add_i8(unum: u8, inum: i8) u8 {
	const num1: i16 = @max(0, @as(i16, unum) + inum);
	const num2: i8 = @truncate(num1);
	return @bitCast(num2);
}
